﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Text;

namespace RWXMLEdit.Common
{
    public class CsvHelper
    {
        public static List<CsvInfo> GetAllAttributes()
        {
            var items = new List<CsvInfo>();

            //Add item for TractiveForceVSpeed
            var item1 = new CsvInfo
                            {
                                Id = new Guid("F0103A3B-414F-4ac9-B681-1E00A4CF1498"),
                                Text = "TractiveForceVSpeed",
                                XData = "Speed (mph)",
                                YData = "TE (kN)"
                            };

            items.Add(item1);

            //Add item for TractiveEffortVThrottle
            var item2 = new CsvInfo
                            {
                                Id = new Guid("059075EE-605A-421e-A35D-F9E11D99EEE1"),
                                Text = "TractiveEffortVThrottle",
                                XData = "Throttle (%)",
                                YData = "TE (kN)"
                            };

            items.Add(item2);

            //Add item for TractiveEffortVFieldDivertProportion
            var item3 = new CsvInfo
                            {
                                Id = new Guid("460376E3-0FE7-4ff5-BA5C-D4AB84503C7B"),
                                Text = "TractiveEffortVFieldDivertProportion",
                                XData = "Field Divert (0.0 - 1.0)",
                                YData = "TE (kN)"
                            };

            items.Add(item3);

            return items;
        }

        public static DataTable OpenWaggonCsvFile(string path)
        {
            return null;
        }

        public static bool SaveCsvFile(DataTable table, bool header, bool quoteall, string path)
        {
            DataView dataView = table.DefaultView;

            dataView.Sort = "X-Value ASC";

            table = dataView.ToTable();

            DataTable dataTable = ConvertTableToString(table);

            string test = WriteToString(dataTable, header, quoteall);

            TextWriter tw = new StreamWriter(path);

            try
            {
                tw.Write(test);
            }
            catch (Exception ex)
            {
                throw new CsvException(ex.Message, path);
            }
            finally
            {
                tw.Close();
            }

            return true;
        }

        private static void CreateMissingCsvFile(string path)
        {
            TextWriter tw = null;

            try
            {
                tw = new StreamWriter(path);
                
                tw.WriteLine("0,0");
            }
            catch (Exception ex)
            {
                throw new CsvException(ex.Message, path);
            }
            finally
            {
                if(tw != null)
                    tw.Close();
            }
        }

        public static DataTable OpenCsvFile(string path)
        {
            TextReader tr = null;

            if(!File.Exists(path))
            {
                try
                {
                    CreateMissingCsvFile(path);
                }
                catch (CsvException ex)
                {
                    throw new CsvException(ex.Message, ex.FileName);
                }
            }

            try
            {
                tr = new StreamReader(path);

                DataTable temp = Parse(tr, false);

                temp.Columns[0].ColumnName = "X-Value";

                temp.Columns[1].ColumnName = "Y-Value";

                return ConvertTableToDouble(temp);
            }
            catch (Exception ex)
            {
                throw new CsvException(ex.Message, path);
            }
            finally
            {
                if(tr != null)
                    tr.Close();
            }
        }

        private static DataTable Parse(TextReader stream, bool headers)
        {
            var table = new DataTable();
            var csv = new CsvStream(stream);

            string[] row = csv.GetNextRow();

            if (row == null)
                return null;
            if (headers)
            {
                foreach (string header in row)
                {
                    if (!String.IsNullOrEmpty(header) && !table.Columns.Contains(header))
                        table.Columns.Add(header, typeof (string));
                    else
                        table.Columns.Add(GetNextColumnHeader(table), typeof (string));
                }
                row = csv.GetNextRow();
            }
            while (row != null)
            {
                while (row.Length > table.Columns.Count)
                    table.Columns.Add(GetNextColumnHeader(table), typeof (string));
                table.Rows.Add(row);
                row = csv.GetNextRow();
            }
            return table;
        }

        private static DataTable ConvertTableToString(DataTable oldTable)
        {
            var newTable = new DataTable("CSV_FILE");

            var columnX = new DataColumn("X-Value", typeof(string));
            var columnY = new DataColumn("Y-Value", typeof(string));
            
            newTable.Columns.Add(columnX);
            newTable.Columns.Add(columnY);

            foreach (DataRow row in oldTable.Rows)
            {
                string x = row[0].ToString().Replace(",", ".");
                string y = row[1].ToString().Replace(",", ".");

                newTable.Rows.Add(x, y);
            }

            return newTable;
        }

        private static DataTable ConvertTableToDouble(DataTable oldTable)
        {
            var newTable = new DataTable("CSV_FILE");

            var columnX = new DataColumn("X-Value", typeof(double));
            var columnY = new DataColumn("Y-Value", typeof(double));
            
            newTable.Columns.Add(columnX);
            newTable.Columns.Add(columnY);

            foreach(DataRow row in oldTable.Rows)
            {
                double x;
                double y;

                double.TryParse(row[0].ToString(), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture.NumberFormat, out x);

                double.TryParse(row[1].ToString(), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture.NumberFormat, out y);
                
                newTable.Rows.Add(x, y);
            }

            return newTable;
        }

        private static string GetNextColumnHeader(DataTable table)
        {
            int c = 1;
            while (true)
            {
                string h = "Column" + c++;
                if (!table.Columns.Contains(h))
                    return h;
            }
        }

        private static string WriteToString(DataTable table, bool header, bool quoteall)
        {
            StringWriter writer = new StringWriter();
            WriteToStream(writer, table, header, quoteall);
            return writer.ToString();
        }

        private static void WriteToStream(TextWriter stream, DataTable table, bool header, bool quoteall)
        {
            if (header)
            {
                for (int i = 0; i < table.Columns.Count; i++)
                {
                    WriteItem(stream, table.Columns[i].Caption, quoteall);
                    if (i < table.Columns.Count - 1)
                        stream.Write(',');
                    else
                        stream.Write('\n');
                }
            }
            foreach (DataRow row in table.Rows)
            {
                for (int i = 0; i < table.Columns.Count; i++)
                {
                    WriteItem(stream, row[i], quoteall);
                    if (i < table.Columns.Count - 1)
                        stream.Write(',');
                    else
                        stream.Write('\n');
                }
            }
        }

        private static void WriteItem(TextWriter stream, object item, bool quoteall)
        {
            if (item == null)
                return;
            string s = item.ToString();
            if (quoteall || s.IndexOfAny("\",\x0A\x0D".ToCharArray()) > -1)
                stream.Write("\"" + s.Replace("\"", "\"\"") + "\"");
            else
                stream.Write(s);
        }
    }

    internal class CsvStream
    {
        private readonly TextReader _stream;
        private bool _eos;
        private bool _eol;
        private readonly char[] _buffer = new char[4096];
        private int _pos;
        private int _length;

        public CsvStream(TextReader s)
        {
            _stream = s;
        }

        public string[] GetNextRow()
        {
            var row = new ArrayList();

            while (true)
            {
                string item = GetNextItem();
                if (item == null)
                {
                    return row.Count == 0 ? null : (string[])row.ToArray(typeof(string));
                }
                    
                row.Add(item);
            }
        }

        private string GetNextItem()
        {
            if (_eol)
            {
                // previous item was last in line, start new line
                _eol = false;
                return null;
            }

            bool quoted = false;
            bool predata = true;
            bool postdata = false;
            var item = new StringBuilder();

            while (true)
            {
                char c = GetNextChar(true);

                if (_eos)
                {
                    return item.Length > 0 ? item.ToString() : null;
                }

                if ((postdata || !quoted) && c == ',')
                {
                    // end of item, return
                    return item.ToString();
                }

                if ((predata || postdata || !quoted) && (c == '\x0A' || c == '\x0D'))
                {
                    // we are at the end of the line, eat newline characters and exit
                    _eol = true;
                    if (c == '\x0D' && GetNextChar(false) == '\x0A')
                    {
                        // new line sequence is 0D0A
                        GetNextChar(true);
                    }
                        
                    return item.ToString();
                }

                if (predata && c == ' ')
                {
                    // whitespace preceeding data, discard
                    continue;
                }

                if (predata && c == '"')
                {
                    // quoted data is starting
                    quoted = true;
                    predata = false;
                    continue;
                }

                if (predata)
                {
                    // data is starting without quotes
                    predata = false;
                    item.Append(c);
                    continue;
                }

                if (c == '"' && quoted)
                {
                    if (GetNextChar(false) == '"')
                        // double quotes within quoted string means add a quote       
                        item.Append(GetNextChar(true));
                    else
                        // end-quote reached
                        postdata = true;
                    continue;
                }

                // all cases covered, character must be data
                item.Append(c);
            }
        }

        private char GetNextChar(bool eat)
        {
            if (_pos >= _length)
            {
                _length = _stream.ReadBlock(_buffer, 0, _buffer.Length);
                if (_length == 0)
                {
                    _eos = true;
                    return '\0';
                }
                _pos = 0;
            }
            if (eat)
            {
                return _buffer[_pos++];
            }
                
            return _buffer[_pos];
        }
    }
}
